package org.jobtask.schedule;

import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Observable;
import java.util.Set;

import javax.swing.text.BadLocationException;

import org.jobtask.config.SystemIdentification;
import org.jobtask.iface.Scheduling;
import org.jobtask.io.LogIO;

/**
 * 
 * @author 张波(Bob)
 *@version 1.2<br/>
 *@Date 2011-09-17
 */
public class TimerJob extends Observable implements Scheduling{
	
	/**
	 * @description
	 *声明任务对象,表示当前所要调用的任务对象
	 */
	private JobTask jobTask;
	
	/**
	 * @description
	 * 用于控制线程循环的变量
	 */
	private boolean isStop=false;
	
	/**
	 * @description
	 * 记录标记为"一次性"任务的历史栈,将已经完成的"一次性"任务保存在栈中,只要是在栈中的任务,则不再运行
	 */
	private Set<Date> historyTask=null;
	
	/**
	 * @description
	 * 任务的控制流对象
	 */
	private TaskFlow taskFlow;
	
	/**
	 * @description
	 * 任务启动时的时间,表示问当前时间,这个时间会被用来作为任务启动的参考值
	 */
	private Date currentDate;
	private Map<Date,String> dtMap=null;
	
	/**
	 * @param job
	 * 接受一个job对象,用来初始化本地的任务对象
	 */
	public TimerJob(JobTask job){
		this.jobTask=job;
		historyTask=new HashSet<Date>();
		this.taskFlow=new TaskFlow(job.getCommand());
	}
	
	
	/**
	 * @description
	 * 线程方法,主要功能均在其中实现.
	 * 线程启动时,会记录下当前线程启动的时间,用来作为比较的参考值,本线程的主要功能是控制定时任务的运行,
	 * 每间隔指定时间以后,会检查一次当前时间是否和任务中标记的时间相符,如果相符就表明该任务可以启动,否则就继续等待,
	 * 这其中,会存在"一次性"任务和"每日性"任务,如果所有任务都是一次性任务,当所有任务运行完成以后,线程会停止,如果其中
	 * 有一个是"每日性"任务或全部都是"每日性"任务,则调度不会停止,每天在指定时间均会执行一次该任务,但其中的"一次性"任务
	 * 不会再运行.
	 */
	@Override
	public void run() {
		/*获得当前线程启动时间,该时间一般是有用户在界面上手动启动任务时的时间 */
		currentDate=new Date();
		/*
		 * 得到任务对象中的任务序列,getSartTimes方法每次都会返回任务时间的当前日期,也就是说,任务如果是2011-09-01 15:30:00建立,
		 * 在当天运行完成以后,到第二天(2011-09-02 15:30:00)会再次运行这些任务,不同的是,返回的是,当天的新的日期(2011-09-02),而不是
		 * (2011-09-01)
		 */
		dtMap=this.jobTask.getSartTimes();
		//获得任务列表中的所有任务时间,这也是任务列表中的Key
		Date[] dateSet=this.jobTask.getSortKeys();
		if(!this.taskFlow.begin()) return;
		if(!this.taskFlow.monitor(this.jobTask)){
			this.jobTask.setState(JobState.STOP);
			this.setChanged();
			this.notifyObservers(this.jobTask);
			isStop=true;
			return;
		}
		while(!isStop){
			//每一轮检查时,获得最新的时间,格式为 小时:分钟(MM:ss),也就是说,任务将忽略秒钟,用来作比较,看任务是否运行
			String currenttime=SystemIdentification.parseTimeToString(new Date(), false);
			//循环判断任务是否可以启动,如果循环完成,都没有时间相匹配,则说明任务还没有到时间,等待指定时间后会继续下一轮检查
			for(Date dt:dateSet){
				//循环任务列表,与currenttime比较,如果时间相匹配则启动该任务,否则跳过.
				if(currenttime.equals(SystemIdentification.parseTimeToString(dt, false))){
					//如果是"每日性"任务,则直接运行
					if(dtMap.get(dt).equals("day")){
						if(!this.taskFlow.begin()) break;
						if(!this.taskFlow.monitor(this.jobTask)){
							this.jobTask.setState(JobState.STOP);
							this.setChanged();
							this.notifyObservers(this.jobTask);
							isStop=true;
							return;
						}
					}else{
						//如果是"一次性"任务,侧需要检查,该任务是否已在历史任务栈,如果不在,则说明该任务是新的,需要运行,否则就跳过\.
						if(!this.historyTask.contains(dt)){
							if(!this.taskFlow.begin()) break;
							if(!this.taskFlow.monitor(this.jobTask)){
								this.jobTask.setState(JobState.STOP);
								this.setChanged();
								this.notifyObservers(this.jobTask);
								isStop=true;
								return;
							}
							//当该任务运行完成以后,将任务加入历史任务栈
							this.historyTask.add(dt);
						}
					}
					break;
			    }
		    }
			/*
			 * 如果上面的任务运行完成,或检查完成都没有可运行的任务,则都会进入此步骤,此步骤的作用是,判断线程是否还需要继续,
			 * 因为,如果任务列表中都是"一次性"任务,在所有任务都运行完成以后,线程是需要停止的.如果需要停止,就会通知监视者,停止
			 * 该任务并改变界面状态.
			 */
			if(this.isContinue(dateSet, "one")){
				this.jobTask.setState(JobState.RUNING);
				try {
					Thread.sleep(45*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else{
				this.jobTask.setState(JobState.STOP);
				this.setChanged();
				this.notifyObservers(this.jobTask);
				return;
			}
	    }
   }
	
	/**
	 * 
	 * @param dates
	 * 在dates中检查,任务是否属于一次性任务,如果都是一次性任务,同时在启动时,如果有任务已经超过了任务的
	 * 启动时间,则该任务直接放入历史任务栈
	 * @param value
	 * @return
	 */
	private boolean isContinue(Date[] dates,String value){
		int count=0;
		boolean isSuccess=true;
		for(Date d:dates){
			if(dtMap.get(d).equals(value)){
				if(d.after(currentDate)){
					 if(!this.historyTask.contains(d)){
						 return isSuccess;
					 }
				 }else{
					 count++;
				 }
			 }
		}
		if((this.historyTask.size()+count)==dates.length){
			isSuccess=false;
		}
		return isSuccess;
	}
	

	@Override
	public void halt(){
			this.taskFlow.destroy();
			this.isStop=true;
			if(this.getJobRunState()){
			   this.jobTask.setState(JobState.STOP);
			}else{
				this.jobTask.setState(JobState.PAUSE);
			}
			Runtime.getRuntime().gc();
	}
	
	private void clearCache(){
		try {
			this.jobTask.clearLog(false);
		} catch (BadLocationException e) {
			e.printStackTrace();
		}
	}
	
	public boolean getJobRunState(){
		return this.taskFlow.isSeccess();
	}
}
